package partie3;

/******************************************************
 Laboratoire #3 : Programmation d'un serveur DNS
 
 Cours :             LOG610
 Session :           Hiver 2007
 Groupe :            01
 Projet :            Laboratoire #3
 �tudiant(e)(s) :    Maxime Bouchard
 Code(s) perm. :     BOUM24028309
 
 Professeur :        Michel Lavoie 
 Nom du fichier :    UDPReceiver.java
 Date cr�e :         2007-03-10
 Date dern. modif.   X
 *******************************************************/
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;


/**
 * Cette classe permet la r�ception d'un paquet UDP sur le port de r�ception
 * UDP/DNS. Elle analyse le paquet et extrait le hostname
 * 
 * Il s'agit d'un Thread qui �coute en permanance 
 * pour ne pas affecter le d�roulement du programme
 * @author Max
 *
 */


public class UDPReceiver extends Thread {
	/**
	 * Les champs d'un Packet UDP
	 * --------------------------
	 * En-t�te (12 octects)
	 * Question : l'adresse demand�
	 * R�ponse : l'adresse IP
	 * Autorit� : info sur le serveur d'autorit�
	 * Additionnel : information suppl�mentaire
	 */
	
	/**
	 * D�finition de l'En-t�te d'un Packet UDP
	 * ---------------------------------------
	 * Identifiant Param�tres
	 * QDcount Ancount
	 * NScount ARcount
	 * 
	 *� identifiant est un entier permettant d�identifier la requete.
	 *� parametres contient les champs suivant :
	 *	� QR (1 bit) : indique si le message est une question (0) ou une reponse (1).
	 *	� OPCODE (4 bits) : type de la requete (0000 pour une requete simple).
	 *	� AA (1 bit) : le serveur qui a fourni la reponse a-t�il autorite sur le domaine?
	 *	� TC (1 bit) : indique si le message est tronque.
	 *	� RD (1 bit) : demande d�une requete recursive.
	 *	� RA (1 bit) : indique que le serveur peut faire une demande recursive.
	 *	� UNUSED, AD, CD (1 bit chacun) : non utilises.
	 *	� RCODE (4 bits) : code de retour. 0 : OK, 1 : erreur sur le format de la requete, 2: probleme du serveur,
	 *    3 : nom de domaine non trouve (valide seulement si AA), 4 : requete non supportee, 5 : le serveur refuse
	 *    de repondre (raisons de s�ecurite ou autres).
	 * � QDCount : nombre de questions.
	 * � ANCount, NSCount, ARCount : nombre d�entrees dans les champs �Reponse�, �Autorite�, �Additionnel�.
	 */
	
	/**
	 * Les champs Reponse, Autorite, Additionnel sont tous representes de la meme maniere :
	 *
	 * � Nom (16 bits) : Pour eviter de recopier la totalite du nom, on utilise des offsets. Par exemple si ce champ
	 *   vaut C0 0C, cela signifie qu�on a un offset (C0) de 12 (0C) octets. C�est-a-dire que le nom en clair se trouve
	 *   au 12eme octet du message.
	 * � Type (16 bits) : idem que pour le champ Question.
	 * � Class (16 bits) : idem que pour le champ Question.
	 * � TTL (32 bits) : dur�ee de vie de l�entr�ee.
	 * � RDLength (16 bits): nombre d�octets de la zone RDData.
	 * � RDData (RDLength octets) : reponse
	 */
	
	private DataInputStream d = null;
	protected final static int BUF_SIZE = 1024;
	protected String SERVER_DNS = null;
	protected int port = 53;  // port de r�ception
	private String DomainName = "none";
	private String DNSFile = null;
	private String adrIP = null;
	private boolean RedirectionSeulement = false;
	private String adresseIP = null;
	
	public void setport(int p) {
		this.port = p;
	}
	
	public void setRedirectionSeulement(boolean b){
		this.RedirectionSeulement = b;
	}
	
	public String gethostNameFromPacket(){
		return DomainName;
	}
	
	public String getAdrIP(){
		return adrIP;
	}
	
	private void setAdrIP(String ip){
		adrIP = ip;
	}
	
	public void sethostNameFromPacket(String hostname){
		this.DomainName = hostname;
	}
	
	public String getSERVER_DNS(){
		return SERVER_DNS;
	}
	
	public void setSERVER_DNS(String server_dns){
		this.SERVER_DNS = server_dns;
	}
	
	public void UDPReceiver(String server_dns,int Port) {
		this.SERVER_DNS = server_dns;
		this.port = Port;
	}
	
	public void setDNSFile(String filename){
		DNSFile = filename;
	}
	
	public void run(){

		try{

                    //*Creation d'un socket UDP
                    DatagramSocket serverSocket = null;
                    serverSocket = new DatagramSocket(this.port);

                    //*Boucle infinie de recpetion
                    while(true){

                            //*Reception d'un paquet UDP via le socket
                            byte[] buff= new byte[BUF_SIZE];
                            DatagramPacket rcvPacket = new DatagramPacket (buff, buff.length);
                            serverSocket.receive(rcvPacket);
                            
                            //*Creation d'un DataInputStream ou ByteArrayInputStream pour manipuler les bytes du paquet	
                            byte[] getData = rcvPacket.getData();
                            ByteArrayInputStream rcvBytes = new ByteArrayInputStream(getData);
                            
                            //*Lecture et sauvegarde des deux premier bytes, qui specifie l'identifiant
                            Integer byte1 = rcvBytes.read();
                            Integer byte2 = rcvBytes.read();
                            String id = byte1.toString() + byte2.toString();
                            
                            //*Lecture et sauvegarde du huitieme byte, qui specifie le nombre de reponse dans le message 
                            //rcvBytes.mark(7);
                            rcvBytes.skip(5);
                            Integer nbAnswers = rcvBytes.read();
                            
                            
                            //*Dans le cas d'une reponse
                            if (nbAnswers == 1){
                                    //*Lecture du Query Domain name, a partir du 13 byte
                                    String qDomName = "";
                                    rcvBytes.skip(4);
                                    while(true){
                                        Integer qNameLength = rcvBytes.read(); 
                                        
                                        if (qNameLength>0){
                                            if (!qDomName.isEmpty())
                                                qDomName += ".";

                                            byte[] readData = new byte[qNameLength];
                                            int numData = rcvBytes.read(readData);
                                            qDomName += new String(readData);
                                        }else
                                            break;
                                    }

                                    //*Sauvegarde du Query Domain name
                                    this.DomainName = qDomName;
                                    
                                    //*Passe par dessus Query Type et Query Class
                                    //*Passe par dessus les premiers champs du ressource record pour arriver au ressource data
                                    //*qui contient l'adresse IP associe au hostname (dans le fond saut de 16 bytes)
                                    rcvBytes.skip(16);

                                    //*Capture de l'adresse IP
                                    Integer addrPart1 = rcvBytes.read();
                                    Integer addrPart2 = rcvBytes.read();
                                    Integer addrPart3 = rcvBytes.read();
                                    Integer addrPart4 = rcvBytes.read();
                                    this.adresseIP =  addrPart1.toString() + "." + addrPart2.toString() + "." + addrPart3.toString() + "." + addrPart4.toString();
                                    this.adrIP = this.adresseIP;
                                    
                                    //*Ajouter la correspondance dans le fichier seulement si une seule
                                    //*reponse dans le message DNS (cette apllication ne traite que ce cas)
                                    AnswerRecorder ansRecorder = new AnswerRecorder(this.DNSFile);
                                    ansRecorder.StartRecord(qDomName, this.adresseIP);

                                    //*Faire parvenir le paquet reponse au demandeur original, ayant emis une requete 
                                    //*avec cet identifiant
                                    DatagramPacket answerPacket = new DatagramPacket(buff, buff.length);
                                    UDPAnswerPacketCreator answerCreator = new UDPAnswerPacketCreator();
                                    answerPacket.setData(answerCreator.CreateAnswerPacket(rcvPacket.getData(), this.adresseIP));
                                    answerPacket.setAddress(rcvPacket.getAddress());
                                    answerPacket.setPort(this.port);
                                    
                                    UDPSender senderToDNS = new UDPSender(rcvPacket, this.SERVER_DNS, this.port); 
                                    senderToDNS.setSocket(serverSocket); 
                                    senderToDNS.SendPacketNow();
                                    
                                    System.out.println("Answer sent, nbAnswer==1");

                            }//*Dans le cas d'une requete
                            if(nbAnswers ==0){
                                    //*Lecture du Query Domain name, a partir du 13 byte
                                    String qDomName = "";
                                    rcvBytes.skip(4);
                                    while(true){
                                        Integer qNameLength = rcvBytes.read(); 
                                        
                                        if (qNameLength>0){
                                            if (!qDomName.isEmpty())
                                                qDomName += ".";

                                            byte[] readData = new byte[qNameLength];
                                            int numData = rcvBytes.read(readData);
                                            qDomName += new String(readData);
                                        }else
                                            break;
                                    }

                                    //*Sauvegarde du Query Domain name
                                    this.DomainName = qDomName;
                                    
                                    //*Sauvegarde de l'adresse, du port et de l'identifiant de la requete
                                    this.adresseIP = rcvPacket.getAddress().getHostAddress();
                                    this.adrIP = rcvPacket.getAddress().getHostAddress();
                                    this.port = rcvPacket.getPort();
                                    
                                    //*Si le mode est redirection seulement
                                    if(this.RedirectionSeulement){
                                        //*Rediriger le paquet vers le serveur DNS
                                        UDPSender senderToDNS = new UDPSender(rcvPacket, SERVER_DNS, port); 
                                        senderToDNS.setSocket(serverSocket); 
                                        senderToDNS.SendPacketNow();
                                        System.out.println("Sent to DNS, nbAnswer==0, true");
                                    }    
                                    else{//*Sinon
                                        //*Rechercher l'adresse IP associe au Query Domain name dans le fichier de 
                                        //*correspondance de ce serveur
                                        QueryFinder finder = new QueryFinder(this.DNSFile, qDomName);
                                        String searchResult = finder.StartResearch(qDomName);
                                        
                                        //*Si la correspondance n'est pas trouvee
                                        if(searchResult.toLowerCase().equals("none")){
                                            //*Rediriger le paquet vers le serveur DNS
                                            UDPSender senderToDNS = new UDPSender(rcvPacket, this.SERVER_DNS, this.port); 
                                            senderToDNS.setSocket(serverSocket); 
                                            senderToDNS.SendPacketNow();
                                            System.out.println("Sent to DNS, nbAnswer==0,false");
                                        }
                                        else{//*Sinon
                                            //*Creer le paquet de reponse a l'aide du UDPAnswerPaquetCreator
                                            DatagramPacket answerPacket = new DatagramPacket(buff, buff.length);
                                            UDPAnswerPacketCreator answerCreator = new UDPAnswerPacketCreator();
                                            answerPacket.setData(answerCreator.CreateAnswerPacket(rcvPacket.getData(), searchResult));
                                            answerPacket.setAddress(rcvPacket.getAddress());
                                            answerPacket.setPort(this.port);
                                            serverSocket.send(answerPacket);
                                            //*Placer ce paquet dans le socket
                                            //*Envoyer le paquet
                                            
                                            System.out.println("Answer sent, nbAnswer==0,false");
                                        }
                                    }
                           }
                    }
		}catch(IOException e){
                    e.printStackTrace();
                }
                catch(Exception e){
                    System.err.println("Probl�me � l'ex�cution :");
                    e.printStackTrace(System.err);
		}	
	}
}
